// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import (
	"internal/abi"
	"runtime/internal/sys"
	"unsafe"
)

// cbs存储所有已注册的Go回调。
var cbs struct {
	lock  mutex
	ctxt  [cb_max]winCallback
	index map[winCallbackKey]int
	n     int
}

// winCallback记录有关已注册Go回调的信息。
type winCallback struct {
	fn     *funcval // Go函数
	retPop uintptr  // 对于386 cdecl，返回时要弹出多少字节
	abiMap abiDesc
}

// abiPartKind是abiPart应该采取的操作。
type abiPartKind int

const (
	abiPartBad   abiPartKind = iota
	abiPartStack             // 将值从内存移动到堆栈。
	abiPartReg               // 将值从内存移到寄存器。
)

// abiPart编码调用ABI之间的转换步骤。
type abiPart struct {
	kind           abiPartKind
	srcStackOffset uintptr
	dstStackOffset uintptr // 如果种类==abiPartStack 
	dstRegister    int     // 如果种类==abiPartReg 
	len            uintptr
}

func (a *abiPart) tryMerge(b abiPart) bool {
	if a.kind != abiPartStack || b.kind != abiPartStack {
		return false
	}
	if a.srcStackOffset+a.len == b.srcStackOffset && a.dstStackOffset+a.len == b.dstStackOffset {
		a.len += b.len
		return true
	}
	return false
}

// abiDesc指定如何从C帧转换为Go 
// 帧。这并没有指定如何回译，因为
// 结果始终是一个uintpttr。如果C ABI是fastcall，则假定四个fastcall寄存器首先溢出到卷影空间。
type abiDesc struct {
	parts []abiPart

	srcStackSize uintptr // stdcall/fastcall堆栈空间跟踪
	dstStackSize uintptr // 使用的Go堆栈空间
	dstSpill     uintptr // 参数溢出槽的额外堆栈空间
	dstRegisters int     // 使用的Go ABI int参数寄存器

	// retOffset是Go 
	// 帧中uintptr大小结果的偏移量。
	retOffset uintptr
}

func (p *abiDesc) assignArg(t *_type) {
	if t.size > sys.PtrSize {
		// 我们现在不支持这个。在
		// stdcall/cdecl中，64位整数和双精度是作为两个字（小端）传递的；而
		// 结构被推送到堆栈上。在
		// fastcall中，通过引用传递大于单词
		// size的参数。在arm上，
		// 8字节对齐的参数向上取整到
		// 下一个偶数寄存器，可以在
		// 寄存器和堆栈之间拆分。
		panic("compileCallback: argument size is larger than uintptr")
	}
	if k := t.kind & kindMask; GOARCH != "386" && (k == kindFloat32 || k == kindFloat64) {
		// 在fastcall中，
		// 中的浮点参数的前四个位置在
		// 浮点寄存器中传递，我们不知道
		// 当前溢出。arm在VFP寄存器中传递浮点
		// 参数，我们也不支持。
		// 所以基本上我们只支持386。
		panic("compileCallback: float arguments not supported")
	}

	if t.size == 0 {
		// Go ABI针对零大小类型对齐。
		p.dstStackSize = alignUp(p.dstStackSize, uintptr(t.align))
		return
	}

	// 在C ABI中，我们已经在单词边界上了。
	// 此外，子字大小的fastcall寄存器参数
	// 存储到
	// 参数字的最低有效字节，所有支持的Windows 
	// 体系结构都是little-endian，因此，srcStackOffset 
	// 已经为较小的
	// 参数指向了正确的位置。手臂也是如此。

	oldParts := p.parts
	if p.tryRegAssignArg(t, 0) {
		// 说明溢出空间。
		// 
		// TODO（mknyszek）：当我们不再有
		// 调用方保留的溢出空间时，删除此选项。
		p.dstSpill = alignUp(p.dstSpill, uintptr(t.align))
		p.dstSpill += t.size
	} else {
		// 寄存器分配失败。
		// 撤消工作和堆栈分配。
		p.parts = oldParts

		// Go ABI对齐参数。
		p.dstStackSize = alignUp(p.dstStackSize, uintptr(t.align))

		// 只复制参数的大小。注意，这个
		// 可能是一个小的按值结构，但是C和Go 
		// 结构布局是兼容的，所以我们也可以直接复制这些
		// 结构。
		part := abiPart{
			kind:           abiPartStack,
			srcStackOffset: p.srcStackSize,
			dstStackOffset: p.dstStackSize,
			len:            t.size,
		}
		// 将此步骤添加到适配器。
		if len(p.parts) == 0 || !p.parts[len(p.parts)-1].tryMerge(part) {
			p.parts = append(p.parts, part)
		}
		// Go ABI包参数。
		p.dstStackSize += t.size
	}

	// cdecl、stdcall、fastcall和arm-pad参数到字大小。
	// TODO（rsc）：在arm和arm64上，我们需要跳过调用方保存的LR吗？
	p.srcStackSize += sys.PtrSize
}

// tryRegAssignArg尝试注册并分配类型为t的值。
// 如果此类型嵌套在聚合类型中，则偏移量是其父类型中此类型的
// 偏移量。
// 假设t.size<=sys.PtrSize和t.size！=0返回赋值是否成功。
func (p *abiDesc) tryRegAssignArg(t *_type, offset uintptr) bool {
	switch k := t.kind & kindMask; k {
	case kindBool, kindInt, kindInt8, kindInt16, kindInt32, kindUint, kindUint8, kindUint16, kindUint32, kindUintptr, kindPtr, kindUnsafePointer:
		// 为所有这些类型分配一个寄存器。
		return p.assignReg(t.size, offset)
	case kindInt64, kindUint64:
		// 仅当寄存器足够大时才分配寄存器。
		if sys.PtrSize == 8 {
			return p.assignReg(t.size, offset)
		}
	case kindArray:
		at := (*arraytype)(unsafe.Pointer(t))
		if at.len == 1 {
			return p.tryRegAssignArg(at.elem, offset)
		}
	case kindStruct:
		st := (*structtype)(unsafe.Pointer(t))
		for i := range st.fields {
			f := &st.fields[i]
			if !p.tryRegAssignArg(f.typ, offset+f.offset()) {
				return false
			}
		}
		return true
	}
	// 当前不支持指针大小的类型，如映射和通道
	// 不支持。
	panic("compileCallabck: type " + t.string() + " is currently not supported for use in system callbacks")
}

// assignReg尝试将给定大小的
// 参数的单个寄存器以给定偏移量分配到C ABI空间中的
// 值中。返回赋值是否成功。
func (p *abiDesc) assignReg(size, offset uintptr) bool {
	if p.dstRegisters >= intArgRegs {
		return false
	}
	p.parts = append(p.parts, abiPart{
		kind:           abiPartReg,
		srcStackOffset: p.srcStackSize + offset,
		dstRegister:    p.dstRegisters,
		len:            size,
	})
	p.dstRegisters++
	return true
}

type winCallbackKey struct {
	fn    *funcval
	cdecl bool
}

func callbackasm()

// callbackasmAddr返回运行时的地址。callbackasm 
// 由i调整的函数。
// 在x86和amd64上，runtime.callbackasm是一系列调用指令，
// 我们希望回调到达
// 对应调用指令，而不是
// runtime.callbackasm的开始。
// 在ARM上，runtime.callbackasm是一系列mov和分支指令。
// R12加载了回调索引。每个条目是两条指令，
// 因此为8字节。
func callbackasmAddr(i int) uintptr {
	var entrySize int
	switch GOARCH {
	default:
		panic("unsupported architecture")
	case "386", "amd64":
		entrySize = 5
	case "arm", "arm64":
		// 在ARM和ARM64上，每个条目都是一条MOV指令
		// 后跟一条分支指令
		entrySize = 8
	}
	return funcPC(callbackasm) + uintptr(i*entrySize)
}

const callbackMaxFrame = 64 * sys.PtrSize

// compileCallback将Go函数fn转换为可以传递到Windows API的C函数指针
// 。
// 
// 在386上，如果cdecl为true，则返回的C函数将使用
// cdecl调用约定；否则，它将使用stdcall。在amd64上，
// 它总是使用fastcall。在arm上，它始终使用arm约定。
// 
// go:linkname compileCallback syscall.compileCallback 
func compileCallback(fn eface, cdecl bool) (code uintptr) {
	if GOARCH != "386" {
		// cdecl仅在386上有意义。
		cdecl = false
	}

	if fn._type == nil || (fn._type.kind&kindMask) != kindFunc {
		panic("compileCallback: expected function with one uintptr-sized result")
	}
	ft := (*functype)(unsafe.Pointer(fn._type))

	// 检查参数并构造ABI翻译。
	var abiMap abiDesc
	for _, t := range ft.in() {
		abiMap.assignArg(t)
	}
	// Go ABI将结果与单词大小对齐。src是
	// 已对齐。
	abiMap.dstStackSize = alignUp(abiMap.dstStackSize, sys.PtrSize)
	abiMap.retOffset = abiMap.dstStackSize

	if len(ft.out()) != 1 {
		panic("compileCallback: expected function with one uintptr-sized result")
	}
	if ft.out()[0].size != sys.PtrSize {
		panic("compileCallback: expected function with one uintptr-sized result")
	}
	if k := ft.out()[0].kind & kindMask; k == kindFloat32 || k == kindFloat64 {
		// 在cdecl和stdcall中，浮点结果在
		// ST（0）中返回。在fastcall中，它们以XMM0返回。
		// 不管怎样，都不是AX。
		panic("compileCallback: float results not supported")
	}
	if intArgRegs == 0 {
		// 为uintptr大小的结果腾出空间。
		// 如果有参数寄存器，返回值将在第一个寄存器中传递。
		abiMap.dstStackSize += sys.PtrSize
	}

	// TODO（mknyszek）：当我们不再有
	// 调用方保留的溢出空间时，从该计算中删除dstSpill。
	frameSize := alignUp(abiMap.dstStackSize, sys.PtrSize)
	frameSize += abiMap.dstSpill
	if frameSize > callbackMaxFrame {
		panic("compileCallback: function argument frame too large")
	}

	// 对于cdecl，被调用方负责从C堆栈弹出其
	// 参数。
	var retPop uintptr
	if cdecl {
		retPop = abiMap.srcStackSize
	}

	key := winCallbackKey{(*funcval)(fn.data), cdecl}

	lock(&cbs.lock) // 我们不会在延迟中解除锁定，因为这是从系统堆栈中使用的。

	// 检查此回调是否已注册。
	if n, ok := cbs.index[key]; ok {
		unlock(&cbs.lock)
		return callbackasmAddr(n)
	}

	// 注册回调。
	if cbs.index == nil {
		cbs.index = make(map[winCallbackKey]int)
	}
	n := cbs.n
	if n >= len(cbs.ctxt) {
		unlock(&cbs.lock)
		throw("too many callback functions")
	}
	c := winCallback{key.fn, retPop, abiMap}
	cbs.ctxt[n] = c
	cbs.index[key] = n
	cbs.n++

	unlock(&cbs.lock)
	return callbackasmAddr(n)
}

type callbackArgs struct {
	index uintptr
	// args指向参数块。
	// 
	// 对于cdecl和stdcall，所有参数都在堆栈上。
	// 
	// 对于fastcall，蹦床将寄存器参数溢出到
	// 堆栈参数下的保留溢出槽，
	// 从而产生与stdcall等效的布局。
	// 
	// 对于arm，蹦床只在堆栈参数下方存储寄存器参数
	// 因此我们可以再次将其视为一个
	// 大堆栈参数帧。
	args unsafe.Pointer
	// 下面是callbackWrap中的out参数
	result uintptr
	retPop uintptr // 对于386 cdecl，callbackasm调用返回时弹出多少字节
}

// callbackWrap调用已注册的C回调。
func callbackWrap(a *callbackArgs) {
	c := cbs.ctxt[a.index]
	a.retPop = c.retPop

	// 从C转换为ABI。
	var regs abi.RegArgs
	var frame [callbackMaxFrame]byte
	goArgs := unsafe.Pointer(&frame)
	for _, part := range c.abiMap.parts {
		switch part.kind {
		case abiPartStack:
			memmove(add(goArgs, part.dstStackOffset), add(a.args, part.srcStackOffset), part.len)
		case abiPartReg:
			goReg := unsafe.Pointer(&regs.Ints[part.dstRegister])
			memmove(goReg, add(a.args, part.srcStackOffset), part.len)
		default:
			panic("bad ABI description")
		}
	}

	// TODO（mknyszek）：当我们不再有
	// 调用方保留的溢出空间时，删除此选项。
	frameSize := alignUp(c.abiMap.dstStackSize, sys.PtrSize)
	frameSize += c.abiMap.dstSpill

	// 即使这是复制回结果，我们也可以传递一个nil 
	// 类型，因为这些结果不需要写屏障。
	reflectcall(nil, unsafe.Pointer(c.fn), noescape(goArgs), uint32(c.abiMap.dstStackSize), uint32(c.abiMap.retOffset), uint32(frameSize), &regs)

	// 提取结果。
	// 
	// 始终只有一个返回值，一个指针大小。
	// 如果它在堆栈上，那么我们将在帧末尾为它保留空间
	// 否则它将在寄存器中传递。
	if c.abiMap.dstStackSize != c.abiMap.retOffset {
		a.result = *(*uintptr)(unsafe.Pointer(&frame[c.abiMap.retOffset]))
	} else {
		var zero int
		// 在没有寄存器的体系结构上，int[0]将是编译错误，因此我们使用动态索引。这些体系结构永远不会接受这个
		// 分支，所以这不会引起运行时恐慌。
		a.result = regs.Ints[zero]
	}
}

const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800

// 如果可用，此函数将使用LoadLibraryEx和文件名
// 参数以及重要的SEARCH_SYSTEM32参数。但是在
// 没有该选项的系统上，absoluteFilepath应该包含一个回退
// 到system32内部的完整路径，以便与vanilla LoadLibrary一起使用。
// go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary 
// go:nosplit 
// go:cgo\u unsafe_args 
func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) {
	lockOSThread()
	c := &getg().m.syscall

	if useLoadLibraryEx {
		c.fn = getLoadLibraryEx()
		c.n = 3
		args := struct {
			lpFileName *uint16
			hFile      uintptr // 始终0 
			flags      uint32
		}{filename, 0, _LOAD_LIBRARY_SEARCH_SYSTEM32}
		c.args = uintptr(noescape(unsafe.Pointer(&args)))
	} else {
		c.fn = getLoadLibrary()
		c.n = 1
		c.args = uintptr(noescape(unsafe.Pointer(&absoluteFilepath)))
	}

	cgocall(asmstdcallAddr, unsafe.Pointer(c))
	handle = c.r1
	if handle == 0 {
		err = c.err
	}
	unlockOSThread() // 在上面的锁线程之后不延迟以保存堆栈帧大小。
	return
}

// go:linkname syscall\u loadlibrary syscall.loadlibrary 
// go:nosplit 
// go:cgo\u unsafe\u args 
func syscall_loadlibrary(filename *uint16) (handle, err uintptr) {
	lockOSThread()
	defer unlockOSThread()
	c := &getg().m.syscall
	c.fn = getLoadLibrary()
	c.n = 1
	c.args = uintptr(noescape(unsafe.Pointer(&filename)))
	cgocall(asmstdcallAddr, unsafe.Pointer(c))
	handle = c.r1
	if handle == 0 {
		err = c.err
	}
	return
}

// go linkname syscall\u getprocaddress syscall.getprocaddress 
// go nosplit 
func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uintptr) {
	lockOSThread()
	defer unlockOSThread()
	c := &getg().m.syscall
	c.fn = getGetProcAddress()
	c.n = 2
	c.args = uintptr(noescape(unsafe.Pointer(&handle)))
	cgocall(asmstdcallAddr, unsafe.Pointer(c))
	outhandle = c.r1
	if outhandle == 0 {
		err = c.err
	}
	return
}

// go:linkname syscall syscall
// go:nosplit 
// go:cgo_unsafe_args 
func syscall_Syscall(fn, nargs, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
	lockOSThread()
	defer unlockOSThread()
	c := &getg().m.syscall
	c.fn = fn
	c.n = nargs
	c.args = uintptr(noescape(unsafe.Pointer(&a1)))
	cgocall(asmstdcallAddr, unsafe.Pointer(c))
	return c.r1, c.r2, c.err
}

// go:linkname syscall_Syscall6 syscall.Syscall6 
// go nosplit 
// go cgo_unsafe_args 
func syscall_Syscall6(fn, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) {
	lockOSThread()
	defer unlockOSThread()
	c := &getg().m.syscall
	c.fn = fn
	c.n = nargs
	c.args = uintptr(noescape(unsafe.Pointer(&a1)))
	cgocall(asmstdcallAddr, unsafe.Pointer(c))
	return c.r1, c.r2, c.err
}

// go:linkname syscall\u Syscall9 syscall.Syscall9 
// go:nosplit 
// go:cgo\u unsafe\u args 
func syscall_Syscall9(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {
	lockOSThread()
	c := &getg().m.syscall
	c.fn = fn
	c.n = nargs
	c.args = uintptr(noescape(unsafe.Pointer(&a1)))
	cgocall(asmstdcallAddr, unsafe.Pointer(c))
	unlockOSThread()
	return c.r1, c.r2, c.err
}

// go:linkname syscall\u Syscall12 Syscall12 Syscall12 
// go:cgo\u unsafe\u args 
func syscall_Syscall12(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 uintptr) (r1, r2, err uintptr) {
	lockOSThread()
	c := &getg().m.syscall
	c.fn = fn
	c.n = nargs
	c.args = uintptr(noescape(unsafe.Pointer(&a1)))
	cgocall(asmstdcallAddr, unsafe.Pointer(c))
	unlockOSThread()
	return c.r1, c.r2, c.err
}

// go:nosplit 
// go:cgo\u unsafe\u args 
func syscall_Syscall15(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
	lockOSThread()
	c := &getg().m.syscall
	c.fn = fn
	c.n = nargs
	c.args = uintptr(noescape(unsafe.Pointer(&a1)))
	cgocall(asmstdcallAddr, unsafe.Pointer(c))
	unlockOSThread()
	return c.r1, c.r2, c.err
}

// go:linkname syscall\u Syscall18 syscall.Syscall18 
// go:nosplit 
// go:cgo\u unsafe\u args 
func syscall_Syscall18(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18 uintptr) (r1, r2, err uintptr) {
	lockOSThread()
	c := &getg().m.syscall
	c.fn = fn
	c.n = nargs
	c.args = uintptr(noescape(unsafe.Pointer(&a1)))
	cgocall(asmstdcallAddr, unsafe.Pointer(c))
	unlockOSThread()
	return c.r1, c.r2, c.err
}
